30DaysChartChallenge

Author

Cristian Guerrero Balber

Categoría: “Comparisons”

Día 1: Part-to-Whole

Audiencia: Grupo de estudiantes de economía
Objetivo: Mostrar los sectores industriales donde se genera más riqueza con todas las posibilidades de estrategia que ello conlleva, como la inversión o el estudio de un sector u otro.
Visualización: Herramienta Datawrapper para realizar un gráfico de rosquilla, convirtiendo los valores netos a porcentajes.
Conjunto de datos: Se realiza una agregación por sector industrial sumando los valores netos por cada registro. La fuente de datos es la siguiente: https://www.kaggle.com/datasets/muhammadehsan02/top-1000-wealthiest-people-in-the-world/data

Día 2: Neo

Audiencia: Grupo de estudiantes de economía
Objetivo: Informar sobre quiénes son las personas que dominan el planeta económicamente y las cantidades de sus riquezas.
Visualización: Herramienta Datawrapper para realizar un gráfico de barras horizontal.
Conjunto de datos: De nuevo, el origen de los datos es el mismo que antes: https://www.kaggle.com/datasets/muhammadehsan02/top-1000-wealthiest-people-in-the-world/data. Se realiza una agregación por nombre de persona y sumando sus riquezas, las cuales pueden proceder de diferentes sectores industriales y, por lo tanto, la misma persona puede aparecer en más de un registro.

Día 3: Makeover

Gráfico generado en PowerBI

Audiencia: Grupo de estudiantes de economía
Objetivo: Mostrar los sectores industriales donde se genera más riqueza con todas las posibilidades de estrategia que ello conlleva, como la inversión o el estudio de un sector u otro.
Visualización: En esta ocasión, se crea un gráfico muy visual y espectacular usando IA Generativa en base a la gráfica del primer día. El gran inconveniente de este tipo de herramientas para la creación de gráficas es el bajo control que se tiene sobre lo que aparece en la imagen por mucho que se refina el prompt. Por ejemplo, se ha distorsionado algunas etiquetas de sectores así como los valores asociados. Es decir, es una herramienta muy potente y espectacular pero aún mejorable desde el punto de vista del control.
Conjunto de datos: Se utiliza como prompt la imagen gráfica del día 1 (Part-to-whole) además del siguiente texto: “Create an image of a pie chart with an original style, including 3D figures related to each industrial sector. The sectors should be represented as follows: Technology (35%) with images such as computers or smartphones, Retail (23%) with images like shopping bags or a storefront, Manufacturing (10%) with icons like gears or a factory, Telecommunications (6%) with symbols such as satellite dishes or cell towers, Media (6%) with elements like film reels or cameras, and Other (22%) with assorted icons that represent various industries. Include the labels with words of sectors and percentages and ensure that they are clear and undistorted.”

Día 4: Waffle

# Instalar y cargar los paquetes
library(waffle)

# Datos
dataset <- read.csv("datasets\\Top_1000_wealthiest_people_d4.csv")

#Se convierten los datos a un vector nombrado
valores <- (setNames(dataset$Net.Worth..in.billions., dataset$Company))

# Crear el gráfico de waffle
waffle_chart <- waffle(valores, rows = 10, size =1,
                       colors = rainbow(length(valores))) +
  labs(title = "Distribución de la riqueza mundial por empresas", subtitle = "Gráfico de waffle de 100 unidades")


# Mostrar el gráfico
waffle_chart

Audiencia: Grupo de estudiantes de economía
Objetivo: Mostrar la distribución de empresas que generan más riquezas a las personas más ricas del planeta. Cuanto más riqueza genera, más cuadrados de color tiene asociado.
Visualización: Se utiliza un waffle chart utilizando la librería de R “waffle”.
Conjunto de datos: El origen de los datos sigue siendo el mismo: https://www.kaggle.com/datasets/muhammadehsan02/top-1000-wealthiest-people-in-the-world/data. En esta ocasión, se realiza una agregación por empresas sumando el valor de la riqueza. Además, se normalizan los datos calculando la proporción sobre 100 de cada empresa y convirtiéndolas en un número entero para obtener este tipo de dato, necesario en tipo y forma para este tipo de gráfica.

Día 5: Diverging

library(ggplot2)

# Datos de ejemplo
datos <- read.csv("datasets\\Inequality in Income Spain Comparison_d5.csv")

datos$Desigualdad <- ifelse(datos$Inequality.in.income..2021. > 0, "Mayor desigualdad que España", "Menor desigualdad que España")

# Crear el gráfico
ggplot(data = datos, aes(x = reorder(Country, Inequality.in.income..2021.), 
                          y = Inequality.in.income..2021., 
                          fill = Desigualdad)) +
  geom_bar(stat = "identity", width = 0.5, color = "black") +
  coord_flip() +  
  scale_fill_manual(values = c("pink", "lightblue")) +
  labs(title = "¿Qué países enfrentan una mayor desigualdad de ingresos en comparación con España?",
       x = "País",
       y = "Diferencia de GINI con respecto a España",
       fill = "Diferencia de desigualdad en ingresos") +
  theme_minimal() + 
  theme(panel.spacing = unit(100, "lines"),
        plot.title = element_text(size = 9, face = "bold")) 

Audiencia: Grupo de estudiantes de economía
Objetivo: Concienciar de la posición que ocupa España con respecto al resto de países del mundo en relación a la desigualdad de ingresos de sus habitantes.
Visualización: Se utiliza la librería “ggplot2” de R. El gráfico elegido es un gráfico horizontal de barras modificando los colores en función de si es mejor o peor el resultado en comparación con España.
Conjunto de datos: https://www.kaggle.com/datasets/iamsouravbanerjee/inequality-in-income-across-the-globe. Se modifica la columna “Inequality in income (2021)”, donde se muestra el GINI por país, para calcular cada una con respecto a España, restándole el valor GINI de España a cada valor GINI del resto de los países. De esta manera, se consigue el efecto deseado y se puede realizar una comparación con el país de interés.

Día 6: OECD (data day)

Gráfico generado en PowerBI

Audiencia: Grupo de estudiantes de economía
Objetivo: Resaltar aquellos continentes con una mayor desigualdad en cuanto a ingresos en sus países.
Visualización: Se realiza a través de PowerBI. Se usa una gráfica de embudo con un degradado de colores para mostrar los resultados de “peor” (mayor GINI promedio) a “mejor” (menor GINI promedio).
Conjunto de datos: Se usa el mismo dataset del día anterior: https://www.kaggle.com/datasets/iamsouravbanerjee/inequality-in-income-across-the-globe. En esta ocasión, se ha realizado el promedio de todos los paises por continente y se ha ordenado de mayor a menor.

Categoría: “Distributions”

Día 7: Hazards

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

# Se carga el dataset
df = pd.read_csv("datasets//annual_deaths_by_causes.csv")

# Se realizan las transformaciones adecuadas para la correcta visualización
df = df[df.code != "OWID_WRL"]
cols_causes = df.columns[3:]
df = pd.melt(df,id_vars = ['country', 'code'],value_vars = cols_causes,var_name = "cause_of_death",value_name = "number_of_people")
df = df.groupby('cause_of_death')['number_of_people'].sum().reset_index()
df.sort_values(by = 'number_of_people', ascending = False, inplace = True)
df.reset_index(drop = True, inplace = True)

# Cálculo de características del boxplot
Q1 = df.number_of_people.quantile(0.25)
Q3 = df.number_of_people.quantile(0.75)
IQR = Q3 - Q1
lim_sup = Q3 + IQR * 1.5
lim_inf = Q1 - IQR * 1.5

# Identifico los outliers
outliers = df[(df.number_of_people > lim_sup) | (df.number_of_people < lim_inf)]

# Se carga el tema "pastel" de sns
sns.set_theme(style="ticks", palette="pastel")
# Se pinta el boxplot
sns.boxplot(y="number_of_people", data=df, palette = ["g"], legend = False)
plt.title("Distribución de muertes mundiales acumuladas por tipo de enfermedad (1990-2019)")

# Etiqueto los outliers
for i, row in outliers.iterrows():
  plt.text(-0.02, row.number_of_people, f'{row.cause_of_death}', color = 'black',
           ha = 'right', va = 'center', fontsize = 8)
# Muestro la figura
plt.show()

Audiencia: Grupo de Médicos del Hospital Universitario Puerta del Mar en Cádiz, España
Objetivo: Se pretende mostrar la distribución de número acumulado de causas de muerte desde 1990 hasta 2019 con el fin de determinar si existen ciertas enfermedades que, en comparación con el resto, tienen una incidencia mucho más alta o baja.
Visualización: Se utiliza la librería matplotlib.pyplot junto con seaborn de python. Se escoge un gráfico boxplot, ideal para identificar visualmente outliers.
Conjunto de datos: https://www.kaggle.com/datasets/madhurpant/world-deaths-and-causes-1990-2019. Previo al graficado de datos, se ha de calcular programáticamente los outliers para que puedan ser etiquetados en el boxplot. De esta manera, la audiencia puede identificar fácilmente aquellas enfermedades que están fuera de la normalidad.

Día 8: Circular

library(tidyr)
library(dplyr)
library(ggplot2)
library(ggrepel)
library(tidyverse)

#Obtengo el dataframe del csv
df <- read.csv("datasets\\annual_deaths_by_causes.csv")

#Extraigo todas las columnas de las causas de muerte
cols_causes <- names(df)[4:ncol(df)]

#Se transforma el dataframe
df_transformed <- df %>%
  pivot_longer(cols = cols_causes, 
               names_to = "cause_of_death",
               values_to = "number_of_people") %>%
  filter(country == "Spain")  %>%
  group_by(cause_of_death) %>%
  summarise(total_people = sum(number_of_people)) %>%
  arrange(desc(total_people)) %>%
  drop_na()

#Se añaden los porcentajes
total_sum <- sum(df_transformed$total_people)
df_transformed <- df_transformed %>%
  mutate(percentage = round((total_people / total_sum)*100, 0))
#Se divide el df en dos partes: las primeras 5 filas y el resto
df_top5 <- df_transformed[1:5, ]
df_others <- df_transformed[6:nrow(df), ]

#Sumar los valores de otros
df_others_summary <- df_others %>%
  summarise(cause_of_death = "Others",
            total_people = sum(total_people, na.rm = TRUE),
            percentage = sum(percentage, na.rm = TRUE))

#Se combinan los primeros 5 valores y el grupo "Otros"
df_final <- bind_rows(df_top5, df_others_summary)

#Se realiza un gráfico de sectores con etiquetas fuera del gráfico

# Obtener las posiciones
df2 <- df_final %>% 
  mutate(csum = rev(cumsum(rev(percentage))), 
         pos = percentage/2 + lead(csum, 1),
         pos = if_else(is.na(pos), percentage/2, pos))

ggplot(df_final, aes(x = "" , y = percentage, fill = fct_inorder(cause_of_death))) +
  geom_col(width = 1, color = 1) +
  coord_polar(theta = "y") +
  scale_fill_brewer(palette = "Pastel1") +
  geom_label_repel(data = df2,
                   aes(y = pos, label = paste0(percentage, "%")),
                   size = 4.5, nudge_x = 1, show.legend = FALSE) +
  guides(fill = guide_legend(title = "Grupo")) +
  ggtitle("Distribución de Causas de Muerte en España") +
  theme_void() 

Audiencia: Grupo de Médicos del Hospital Universitario Puerta del Mar en Cádiz, España
Objetivo: Focalizar los esfuerzos de investigación para las enfermedades más relevantes
Visualización: Se utiliza ggplot2 con la librería ggrepel. Con esto se consigue un gráfico de sectores con el etiquetado fuera del propio gráfico con una visualización, como resultado, más clara.
Conjunto de datos: Origen: https://www.kaggle.com/datasets/madhurpant/world-deaths-and-causes-1990-2019. Se necesita transformar el dataset original cambiando la estructura, pasando las columnas de las enfermedades a una sola columna como “causes_of_death” y sus valores a otra columna llamada “number_of_people”. A partir de aquí, se realizan transformaciones varias de limpieza, agregaciones y sumas, para obtener los valores mostrados. Se comprueba que España tiene una distribución similar a la mundial, dado que las tres grandes enfermedades que afectan a este país son las mismas.

Día 9: Major/Minor

Gráfico generado en PowerBI

Audiencia: Grupo de Médicos del Hospital Universitario Puerta del Mar en Cádiz, España
Objetivo:  Mostrar los países con mayor y menor incidencia en muertes debido a neoplasia (cáncer) en 2019 con el fin de reflexionar sobre qué puede llevar a dichos países a alcanzar estas cifras. Visualización: PowerBI. Se escoge dos visualizaciones de tipo Tarjeta para mostrar el país así como la cifra de incidencias por cada 100 mil habitantes.
Conjunto de datos: https://www.kaggle.com/datasets/madhurpant/world-deaths-and-causes-1990-2019, https://worldpopulationreview.com/. Para poder realizar dicha visualización, con PowerBI se importan los datos preprocesados desde Python realizando un derretido de las columnas de enfermedades. Luego, se crean campos calculados para mostrar los datos deseados, el país con mayor/menor número de muertes por cada 100 mil habitantes y la cifra. Además, se añaden ciertos filtros para que solo se tenga en cuenta la enfermedad de neoplasia y el año 2019 (fecha más actual de la que se tienen datos).

Día 10: Physical

Audiencia: Grupo de Médicos del Hospital Universitario Puerta del Mar en Cádiz, España
Objetivo: Transmitir una visión general de la distribución de afección de la enfermedad con la incidencia más alta de muertes en el planeta.
Visualización: Datawrapper. Así como los mapas físicos muestran con colores las propiedades físicas de un mapa, se utiliza el mismo concepto para mostrar la incidencia de muertes por enfermedades cardiovasculares por país (por cada 100 mil habitantes).
Conjunto de datos: https://www.kaggle.com/datasets/madhurpant/world-deaths-and-causes-1990-2019, https://worldpopulationreview.com/. Para poder realizar dicha visualización, primero se preprocesan los datos con Python realizando un derretido de las columnas de enfermedades. Luego, se importan los datos a PowerBI, donde se realizará una conexión entre dos tablas de dos fuentes de datos diferentes (mencionadas anteriormente). Estas tienen como campo identificativo el país. A partir de aquí, se puede añadir la cantidad de habitantes por país y realizar el cálculo de número de muertes por cada 100 mil habitantes. Además, se añaden ciertos filtros para que solo se tenga en cuenta las enfermedades cardiovasculares y el año 2010 (fecha más actual de la que se tienen datos para combinar en ambos datasets). Una vez se preprocesan los datos, se importan a Datawrapper para poder crear el mapa interactivo y poder embeberlo en RStudio con Quarto.

Día 11: Mobile-friendly

Audiencia: Grupo de Médicos del Hospital Universitario Puerta del Mar en Cádiz, España
Objetivo:
Visualización: Datawrapper. Se usa esta herramienta por tener la opción de embeber de forma interactiva y, además, hacerlo adaptativo para móviles. Para ello, se ha optado por una visualización en tabla con degradado por gravedad de incidencia. Para hacerlo “mobile-friendly” se activan las opciones “Mobile fallback” y “Compact layout”. Además, se le da interactividad mediante la activación de búsqueda de países mediante la opción “Make searchable”.
Conjunto de datos: https://www.kaggle.com/datasets/madhurpant/world-deaths-and-causes-1990-2019, https://worldpopulationreview.com/. Para esta visualización, se aprovecha todo el procesamiento de las gráficas anteriores con la diferencia que se ha filtrado por una enfermedad distinta, “chronic_respiratory_diseases”, que es la tercera con más incidencia en el planeta.

Día 12: Reuters Graphics (theme day)

Audiencia: Grupo de Médicos del Hospital Universitario Puerta del Mar en Cádiz, España
Objetivo: Reflexionar sobre qué es lo que está ocurriendo en el mundo para que el número de fallecidos mundiales a causa de ciertas enfermedades haya aumentado en las últimas décadas.
Visualización: Datawrapper. Se usa un gráfico de líneas para visualizar la evolución de las enfermedades. Al usar esta herramienta embebida, se da la posibilidad a la audiencia de revisar los datos de forma interactiva.
Conjunto de datos: https://www.kaggle.com/datasets/madhurpant/world-deaths-and-causes-1990-2019. Se preparan los datos eliminando las columnas de “country” y de “code”. Además, se realiza una agrupación por “year” para graficar la evolución. Se calcula la media del ratio de crecimiento de la población mundial en base a https://worldpopulationreview.com/.

Categoría: “Relationships”

Día 13: Family

#Se usará la librería igraph
library(dplyr)
library(ggplot2)

# Se carga el df
df <- read.csv('datasets\\used_cars_data.csv')

#Se seleccionan las instancias a graficar
df1 <- select(df, Year, Fuel_Type, Price)

#Se eliminan valores faltantes
df1 <- na.omit(df1)

#Se grafica la cantidad de datos existentes según el tipo de Fuel
library(ggthemes)
library(patchwork)
library(scales)
ggplot(df1, aes(Year, fill = Fuel_Type)) +
  geom_bar(position = 'fill') + 
  scale_y_continuous(labels = percent) +
  theme_solarized() +
  plot_annotation(title = "¿Qué tipo de coche según el tipo de Fuel predomina más?",
                  caption = "Cristian Guerrero Balber| Datos: used_cars_data.csv (kaggle)")

Audiencia: Conferencia sobre Energía sostenible

Objetivo: Mostrar la cantidad de instancias por familia de Fuel. Esta visualización quiere mostrar el reflejo de la proporción de coches según el Fuel que existen (ojo, esto puede setar sesgado por la fuente de datos).

Visualización: Se utiliza la de la librería ggplot de R. Se usa la extensión ggtheme para darle un aspecto visual atractivo y original. Además, se usa el gráfico de barras apiladas para mostrar la proporción de instancias optimizando la cantidad de código.

Conjunto de datos: Se procesan los datos simplemente seleccionando las características a vincular y limpiándo el df resultante de nulos. Fuente: https://www.kaggle.com/datasets/ayushparwal2026/cars-dataset/data

Día 14: Heatmap

#Se usará la librería ggplot2
library(ggplot2)
library(dplyr)
library(reshape2)
library(ggthemes)

# Se carga el df
df <- read.csv('datasets\\used_cars_data.csv')

#Se seleccionan las características a comparar
df1 <- select(df, Year, Kilometers_Driven, Fuel_Type, Transmission, 
              Owner_Type, Seats, Price)

#Se realiza una codificación de características no numéricas
# Supongamos que df es tu dataset y 'columna_categorica' es una columna no numérica
df1$Fuel_Type <- as.numeric(as.factor(df1$Fuel_Type))
df1$Transmission <- as.numeric(as.factor(df1$Transmission))
df1$Owner_Type <- as.numeric(as.factor(df1$Owner_Type))

#Se calcula la matriz de correlaciones
df_corr <- cor(df1, use = "complete.obs")

#Se convierte la matriz a formato largo
df_corr <- melt(df_corr)

# Crear el gráfico de la matriz de correlaciones
ggplot(data = df_corr, aes(x = Var1, y = Var2, fill = value)) +
  geom_tile() +
  scale_fill_gradient2(low = "blue", high = "red", mid = "white", 
                       limit = c(-1, 1), name = "Coef. de Pearson") +
  geom_text(aes(label = round(value, 2)),  # Redondear y mostrar los valores
            color = "black",               # Color del texto
            size = 4)  +
  theme_pander() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  labs(title = "¿Qué característica tiene mayor correlación sobre el precio?", 
       x = "Características", 
       y = "Características",
       caption = "Cristian Guerrero Balber| Datos: used_cars_data.csv (kaggle)") +
  theme(plot.title = element_text(size = 10))

Audiencia: Conferencia sobre Energía sostenible Objetivo: Mostrar las diferentes correlaciones entre distintas características y el precio. Concretamente, y en el tipo de conferencia que es, parece ser que el tipo de fuel y el precio no tiene una correlación lineal demasiado alta, mientras que la característica que tiene una mayor correlación con el precio es el tipo de transmisión. Visualización: Se utiliza un heatmap de ggplot2 en R Conjunto de datos: Se procesan los datos seleccionando las características a correlacionar. Se codifica en forma de número las columnas que no tienen un valor numérico. Luego, se obtiene la matriz de correlaciones y, por último, se grafica. Fuente: https://www.kaggle.com/datasets/ayushparwal2026/cars-dataset/data

Día 15: Historical

#Se usará la librería igraph
library(dplyr)
library(ggplot2)

# Se carga el df
df <- read.csv('datasets\\used_cars_data.csv')

#Se seleccionan las instancias a graficar
df1 <- select(df, Year, Fuel_Type, Price)

#Se eliminan valores faltantes
df1 <- na.omit(df1)

#Se itera entre los distintos tipos de fuel para ver si hay una cantidad
#suficientemente representativa de cada uno para graficar
fuel_types <- c(unique(df$Fuel_Type))
for (fuel_type in fuel_types){
  df_ <- df1 %>%
        filter(Fuel_Type == fuel_type) %>%
        select(Fuel_Type)
  cat("% de instancias para", fuel_type, ":", 
      round((nrow(df_) / nrow(df1)) * 100, 2), "%\n")
}
% de instancias para CNG : 0.93 %
% de instancias para Diesel : 53.25 %
% de instancias para Petrol : 45.62 %
% de instancias para LPG : 0.17 %
% de instancias para Electric : 0.03 %
library(ggthemes)
library(patchwork)
#Se filtra solo por los más representativos
df2 <- df1 %>%
  filter(Fuel_Type == 'Diesel' | Fuel_Type == 'Petrol')

ggplot(df2, aes(Year, Price, colour = Fuel_Type)) +
  geom_point() + 
  stat_smooth(method = 'lm') +
  scale_x_log10() +
  scale_y_log10() +
  theme_solarized(light = FALSE) +
  labs(title = "¿Qué me costará más, Diesel o Gasolina (Petrol)?",
       caption = "Cristian Guerrero Balber| Datos: used_cars_data.csv (kaggle)")

Audiencia: Conferencia sobre Energía sostenible

Objetivo: Mostrar la evolución del precio en los últimos años de los coches de tipo Diesel y Gasolina.

Visualización: Se usa RStudio con ggplot, con un estilo de la extensión ggthemes. Además, se visualiza por colores los distintos tipos de Fuel. Se ha usado la transformación logaritmica para minimizar el efecto visual de los outliers.

Conjunto de datos: Se procesan los datos seleccionando las características a graficar. Se seleccionan los tipos de fuel más representativos en el dataset. Fuente: https://www.kaggle.com/datasets/ayushparwal2026/cars-dataset/data

Día 16: Weather

#Se usará la librería igraph
library(dplyr)
library(ggplot2)

# Se carga el df
df <- read.csv('datasets\\2021.Vans_Aggregated.csv')

#Se grafica la cantidad de datos existentes según el tipo de Fuel
library(ggthemes)

ggplot(df, aes(Fuel.Type, WLTP.CO2.emissions.weighted..g.km., fill = Fuel.Type)) +
  geom_violin() + 
  geom_boxplot(width = 0.05) +
  theme_economist() +
  scale_fill_discrete(labels = c('Diesel', 'Gasolina', 'Híbrido'),
                      name = 'Tipo de combustible') +
  theme(axis.text.x = element_blank(),
        legend.position = 'right') +
  labs(y = "Emisiones CO2 emitidas en g/km",
       title = "¿Qué tipo de coche según el Fuel predomina más?",
       caption = "Cristian Guerrero Balber| Datos: real-world-vehicle-emissions.csv (kaggle)")

Audiencia: Conferencia sobre Energía sostenible

Objetivo: Mostrar la gran diferencia de la distribución de emisiones de CO2

Visualización: RStudio con ggplot. Se usa un gráfico de violines junto con boxplots, que muestra no solo la distribución de datos sino la densidad de los mismos por categorías. Se muestra claramente como la distribución de las emisiones de los coches híbridos es, de media, 3 veces más baja que las de diesel y gasolina. Además, la varianza de los datos es menor con cero outliers y bigotes de boxplots muy cortos.

Conjunto de datos: No se realiza preprocesado de los datos salvo su carga y su visualización, ya que este tipo de visualización incorpora el propio tratamiento de los datos. Fuente: https://www.kaggle.com/datasets/konradb/real-world-vehicle-emissions

Día 17: Networks

#Se usará la librería igraph
library(igraph)
library(dplyr)

# Se carga el df
df <- read.csv('datasets\\used_cars_data.csv')

#Se seleccionan las instancias a graficar
df1 <- select(df, Fuel_Type, Price)

#Se discretiza la columna de precio en cuartiles
df1 <- df1 %>%
  mutate(Discreted_price = ntile(Price, 4)) %>%
  select(-Price) %>% #Se elimina la columna Price
  mutate(Discreted_price = recode(Discreted_price,
    "1" = "Bajo",
    "2" = "Bajo-Medio",
    "3" = "Medio-Alto",
    "4" = "Alto"
  ) )

#Se eliminan duplicados
df1 <- na.omit(unique(df1))

# Crear el gráfico de red usando igraph
grafo <- graph_from_data_frame(df1, directed = FALSE)

layout <- layout_with_sugiyama(grafo)

# Configurar los colores de los nodos
V(grafo)$color <- ifelse(V(grafo)$name %in% 
c("Bajo", "Bajo-Medio", "Medio-Alto", "Alto"), "salmon", "lightgreen")

# Dibujar el gráfico de la familia
plot(grafo,
     layout = layout,
     vertex.size = 50, 
     vertex.label.cex = 0.8,
     vertex.label.color = "black",  # Color del texto
     vertex.frame.color = NA,  # Sin borde en los nodos
     main = "Red Familia Precios")
legend("bottomleft",
       legend = c("CNG = Compressed Natural Gas",
                  "LPG = Gas Licuado de Petróleo"),
       cex = 0.8,
       bty = 'n')

Audiencia: Conferencia sobre Energía sostenible

Objetivo: Mostrar relaciones en forma de red de precios según el tipo de fuel. Concretamente, concienciar que no existen conexiones entre la energía eléctrica y los coches de precios asequibles según este dataset.

Visualización: Se utiliza la de la librería igraph de R un tipo de gráfico concreto para establecer relaciones en redes. Ideal para mostrar relaciones entre tipo o familias de características.

Conjunto de datos: Se procesan los datos seleccionando las características a vincular y limpiándo el df resultante de nulos. Además, se discretiza la columna Price para obtener una escala cualitativa del precio y poder relacionarla con el tipo de fuel. Fuente: https://www.kaggle.com/datasets/ayushparwal2026/cars-dataset/data

Día 18: Asian Development Bank (data day)

#Se usará la librería igraph
library(dplyr)
library(ggplot2)
library(lubridate)
library(tvthemes)

# Se carga el df
df <- read.csv('datasets\\ADB Climate Change Financing - 2023-based on commitments.csv')

#Se transforma la columna de fecha de texto a fecha, y se limpia el df de nulos
df1 <- df %>%
  select(Date.Signed, Sector, Signed.amount....million.) %>%
  mutate(Signed.amount = suppressWarnings(as.numeric(Signed.amount....million.))) %>%
  select(-Signed.amount....million.)%>%
  mutate(Date.Signed = parse_date_time(Date.Signed, orders = 'd-b-y')) %>%
  mutate(Date.Signed = as.Date(Date.Signed))%>%
  arrange(Date.Signed) %>%
  na.omit()

#Se cambia la fecha a semanas
df1 <- df1 %>%
  mutate(Week.Signed = floor_date(Date.Signed, "week"))%>%
  group_by(Week.Signed, Sector) %>%
  summarise(Signed.amount = sum(Signed.amount)) %>%
  ungroup()

#Se calcula el acumulado por semana y sector
df1 <- df1 %>%
  group_by(Sector) %>%                         
  mutate(Signed.amount.cumsum = cumsum(Signed.amount)) %>%  
  ungroup()

#Se grafica la cantidad de datos existentes según el tipo de Fuel
library(ggthemes)
ggplot(df1, aes(Week.Signed, Signed.amount.cumsum, fill = Sector))+
  geom_area(alpha = 0.8, colour = 'darkblue') +
  theme_simpsons(title.font = "Lucida Sans Unicode",
                 text.font = "Akbar")+
  scale_x_date(breaks = seq(min(df1$Week.Signed), max(df1$Week.Signed), "1 month"),
               date_labels = "%b")+
  scale_y_continuous(labels = scales::dollar)+
  scale_fill_simpsons() +
  labs(y = "Acumulador en millones de $ americanos",
       x = "2023",
       title = "¿Qué sector está marcando la diferencia en la lucha contra el cambio climático?",
       caption = "Cristian Guerrero Balber| Datos: used_cars_data.csv (kaggle)")

Audiencia: Conferencia sobre Energía sostenible

Objetivo: Mostrar las principales áreas económicas que aportan financiación en la lucha contra el cambio climático según un acuerdo firmado en 2015 por la Asian Development Bank (ADB).

Visualización: Se usa RStudio con ggplot. Se importa un estilo muy original de películas, en este caso, de los simpson, para mostrar unas áreas apiladas por sector de la aportación en dólares americanos.

Conjunto de datos: Se preprocesan los datos limpiando de nulos, agrupando por fechas y sectores, así como calculando los acumulados aportados por sector y fecha. Fuente: https://data.adb.org/dataset/climate-change-financing-adb

Categoría: “Timeseries”

Día 19: Dinosaurs

import pandas as pd
from pathlib import Path

ruta = "datasets//dinosaurs.csv"
dataset = pd.read_csv(ruta)
#Se muestra info de la tabla
print(dataset.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 309 entries, 0 to 308
Data columns (total 10 columns):
 #   Column    Non-Null Count  Dtype 
---  ------    --------------  ----- 
 0   name      309 non-null    object
 1   diet      309 non-null    object
 2   period    309 non-null    object
 3   lived_in  308 non-null    object
 4   type      309 non-null    object
 5   length    291 non-null    object
 6   taxonomy  309 non-null    object
 7   named_by  309 non-null    object
 8   species   304 non-null    object
 9   link      309 non-null    object
dtypes: object(10)
memory usage: 24.3+ KB
None
#Existen variables como lived_in, length y species con nulos. Como se va a usar lengh para graficar, se eliminan los nulos
dataset = dataset.dropna(axis = 0)
#Se analizan los diferentes tipos de dinosaurios según su dieta
print(f"Tipos de dinosaurios según su dieta:")
Tipos de dinosaurios según su dieta:
dataset['diet'].unique()
array(['herbivorous', 'carnivorous', 'omnivorous', 'unknown',
       'herbivorous/omnivorous'], dtype=object)
#El resultado 'herbivorous/omnivorous' parece una incongruencia, ya que si es onminoro entonces es herbivoro y carnivoro. Veamos cuantos registros hay.
print(f"\nDinosaurios de tipo herbivorous/omnivorous:")

Dinosaurios de tipo herbivorous/omnivorous:
dataset[dataset['diet'] == 'herbivorous/omnivorous']
            name  ...                                               link
237  riojasaurus  ...  https://www.nhm.ac.uk/discover/dino-directory/...

[1 rows x 10 columns]
#Además, veamos los unknown
#Como hay pocos registros, se eliminan
dataset = dataset[(dataset['diet']!='herbivorous/omnivorous')&(dataset['diet']!='unknown')]
#Se verifica el resultado
dataset['diet'].unique()
array(['herbivorous', 'carnivorous', 'omnivorous'], dtype=object)
#Por inspección visual de la columna "period", se comprueba que tiene casi siempre la misma estructura
dataset['period'].head()
0      Early Jurassic 199-189 million years ago
1       Late Cretaceous 74-70 million years ago
2       Late Cretaceous 83-70 million years ago
3       Late Cretaceous 99-84 million years ago
4    Early Cretaceous 115-105 million years ago
Name: period, dtype: object
#La estructura: premura del periodo + periodo + rango(x-y) + "million years ago". Se crea una función para descomponerla y obtener los años de comienzo y fin del periodo
# ¿Son todas así?
dataset['split_parts'] = dataset['period'].apply(lambda x: x.split())
dataset['count_splits'] = dataset['split_parts'].apply(lambda x: len(x))
dataset[dataset['count_splits'] < 6]
           name         diet  ...          split_parts count_splits
100      erketu  herbivorous  ...  [Early, Cretaceous]            2
209  pantydraco  herbivorous  ...    [Early, Jurassic]            2

[2 rows x 12 columns]
#Se filtra solo por los registros con 6 elementos después del split (sigue la estructura tipo)
dataset = dataset[dataset['count_splits'] == 6]
#Se crean las columnas de forma adecuada
dataset['stage'] = dataset['split_parts'].apply(lambda x: x[0])
dataset['period_name'] = dataset['split_parts'].apply(lambda x: x[1])
dataset['range_period'] = dataset['split_parts'].apply(lambda x: x[2])
#Ahora, del range_period, hay que crear el periodo de comienzo y fin, pero hay que comprobar que sigue este formato
print("¿Existe algún registro de rango de período que no contenga el separador '-'?")
¿Existe algún registro de rango de período que no contenga el separador '-'?
print(dataset[~dataset['range_period'].str.contains('-')].shape)
(25, 15)
#Existen registros sin fecha fin de periodo. Se eliminan estas entradas por estandarizar, ya que aun tenemos bastantes registros para graficar
dataset = dataset[dataset['range_period'].str.contains('-')]
#Se crean las columnas start_period y end_period
dataset['start_period'] = dataset['split_parts'].apply(lambda x: int(x[2].split("-")[0]))
dataset['end_period'] = dataset['split_parts'].apply(lambda x: int(x[2].split("-")[1]))
#Por último, la columna de "length" se va a cambiar a numérico
dataset['length'] = dataset['length'].apply(lambda x: float(x.replace("m", "")))
#Se ordena por length
dataset = dataset.sort_values(by = 'length', ascending = False)

#Se va a guardar el dataset para usarse en otro apartado
dataset.to_csv("datasets\\dinosaurs_mod.csv", index = False)

#Se definen los rangos de los periodos
start_periods = dataset.groupby('period_name')['start_period'].max()
end_periods = dataset.groupby('period_name')['start_period'].min()
print("Rangos de los períodos:")
Rangos de los períodos:
print(start_periods)
period_name
Cretaceous    154
Jurassic      208
Triassic      227
Name: start_period, dtype: int64
print(end_periods)
period_name
Cretaceous     67
Jurassic       79
Triassic      205
Name: start_period, dtype: int64
#Como estos períodos deberían ser secuenciales, se va a calcular el punto medio entre periodos solapados para marcar la frontera
mean_point = int((end_periods['Triassic'] + start_periods['Jurassic']) / 2)
end_periods['Triassic'] = mean_point
start_periods['Jurassic'] = mean_point
mean_point = int((end_periods['Jurassic'] + start_periods['Cretaceous']) / 2)
end_periods['Jurassic'] = mean_point
start_periods['Cretaceous'] = mean_point
print("Rangos de los períodos tras procesarlos:")
Rangos de los períodos tras procesarlos:
print(start_periods)
period_name
Cretaceous    116
Jurassic      206
Triassic      227
Name: start_period, dtype: int64
print(end_periods)
period_name
Cretaceous     67
Jurassic      116
Triassic      206
Name: start_period, dtype: int64
#Ahora se van a graficar los 5 dinosaurios más grandes de cada tipo según su dieta
import warnings
warnings.filterwarnings('ignore')
import plotly.graph_objects as go

def create_dinosaur_plot():
  global dataset_filtered
  global start_periods
  global end_periods
  
  # Crear la figura
  fig = go.Figure()
  
  #Colores de lineas por dieta
  colors_diet = {
      'carnivorous': 'rgba(255, 0, 0, 0.5)', # Rojo con 50% de transparencia
      'herbivorous': 'rgba(0, 255, 0, 0.5)', # Verde con 50% de transparencia
      'omnivorous': 'rgba(0, 0, 255, 0.5)',  # Azul con 50% de transparencia
  
  }
  
  dataset_filtered = pd.concat([dataset[dataset['diet'] == 'carnivorous'].head(5),
                                  dataset[dataset['diet'] == 'herbivorous'].head(5),
                                  dataset[dataset['diet'] == 'omnivorous'].head(5)], axis = 0)
  dataset_filtered = dataset_filtered.sort_values(by = 'length', ascending = True)
  
  # Agregar las líneas horizontales (conexión entre puntos) 
  for i, row in dataset_filtered.iterrows():
      category = row[1]
      color = colors_diet[category]
      fig.add_trace(go.Scatter(
          x=[-row[15], -row[16]],  # Coordenadas x para cada grupo (inicio, fin)
          y=[row[5], row[5]],  # Coordenadas y (mismo valor para conectar)
          mode='lines',
          line=dict(color=color, width=8),
          showlegend = False
      ))
  
      # Agregar puntos para la primera categoría (Inicio)
      fig.add_trace(go.Scatter(
          x=[-row[15]],
          y=[row[5]],
          text = [row[0]],
          textposition = 'top center',
          textfont = dict(family = 'Arial Bold'),
          mode='markers+text',
          name='Inicio Período',
          marker=dict(color=color, size=10),
          showlegend = False
      ))
  
      # Agregar puntos para la segunda categoría (Fin)
      fig.add_trace(go.Scatter(
          x=[-row[16]],
          y=[row[5]],
          mode='markers',
          name='Fin Período',
          marker=dict(color=color, size=10),
          showlegend = False
      ))
  
  #Agregar un rectánculo para cada períodos
  colors_period = {
      'Cretaceous': 'rgb(165, 70, 87)',
      'Jurassic': 'rgb(76, 106, 146)',
      'Triassic': 'rgb(53, 94, 59)'
  }
  for period in start_periods.index:
      fig.add_shape(
          type = "rect",
          x0 = -start_periods[period], x1 = -end_periods[period],
          y0 = min(dataset_filtered['length']) - 5,
          y1 = max(dataset_filtered['length']) + 5,
          fillcolor = colors_period[period],
          opacity = 0.3,
          layer = "below",
          line = dict(color = 'black', width = 3)
      )
  
  # Agregar texto a los rectángulos
      fig.add_annotation(
          x=(-start_periods[period] + -end_periods[period]) / 2,  # Centro en el eje x
          y=max(dataset_filtered['length']) + 4,  # Top en el eje y
          text=period,
          showarrow=False,
          font=dict(size=16, color=colors_period[period]),  # Propiedades de la fuente
          align='center'  # Alineación del texto
      )
  
  # Agregar un trace para cada categoría en la leyenda
  for categoria, color in colors_diet.items():
      fig.add_trace(go.Scatter(
          x=[None],  # No hay coordenadas, solo se usa para la leyenda
          y=[None],
          mode='lines',
          line=dict(color=color, width=4),  # Color correspondiente
          name=categoria,  # Nombre que aparecerá en la leyenda
          showlegend=True  # Este trace aparecerá en la leyenda
      ))
  
  # Configurar el layout
  fig.update_layout(
      title={
        'text': '¿En qué período vivieron los dinosaurios más grandes que hayan pisado la faz de la Tierra?',
        'font': {
          'size': 15
          }
        },
      xaxis_title='Millones de años atrás...',
      yaxis_title='Metros de Altura',
      width = 920,
      height = 700
  )
  
  # Mostrar la gráfica
  #fig.show()
  return fig

fig = create_dinosaur_plot()
fig.show()

Audiencia: Paleontólogos
Objetivo: Mostrar de forma clara los dinosaurios más grandes que han vivido en la tierra divididos por tipo de dieta (carnívoros, herbívoros y omnívoros) así como el período en el que existieron (triásico, jurásico y cetácico).
Visualización: Se usa la librería Plotly de Python. El tipo de gráfica escogida es una dumbbell plot. Se ha mostrado en sus ejes los metros de altura del dinosaurio contra los millones de años atrás en los que vivio. Cada dumbbell representa una franja de tiempo. Además, se ha dividido cada período por colores y se ha añadido una leyenda por color según el tipo de dieta del dinosaurio. Por último, se ha añadido el nombre del dinosaurio sobre su primer punto. De esta manera, se ha conseguido representar claramente hasta 5 dimensiones en un solo gráfico 2D.
Conjunto de datos: Ha sido necesario un prepreocesamiento moderado debido a que los datos de partida no tenian el formato adecuado (tratamiento de textos y números). Además, se ha tenido que modificar información sobre los comienzos de inicio y fin de períodos para que fuera coherente en el gráfico, por lo que esto ha podido alterar la veracidad e integridad de los mismos. Fuente:https://www.kaggle.com/datasets/kjanjua/jurassic-park-the-exhaustive-dinosaur-dataset

Día 20: Correlation

import pandas as pd
import matplotlib.pyplot as plt

ruta = "datasets/dinosaurs_mod.csv"
dataset = pd.read_csv(ruta)
dataset.columns
Index(['name', 'diet', 'period', 'lived_in', 'type', 'length', 'taxonomy',
       'named_by', 'species', 'link', 'split_parts', 'count_splits', 'stage',
       'period_name', 'range_period', 'start_period', 'end_period'],
      dtype='object')
dataset.drop(['name', 'period', 'type', 'lived_in', 'taxonomy', 'named_by', 'species', 'link', 'split_parts', 'count_splits', 'stage', 'range_period', 'start_period', 'end_period'], axis = 1, inplace = True)
#Se discretiza la longitud de los dinosaurios según sus cuartiles
dataset['length'] = pd.qcut(dataset['length'], q = 4, labels=['Pequeños', 'Medianos', 'Grandes', 'Muy grandes'])
agg = dataset.groupby(['diet', 'length']).size().reset_index(name = 'count')

def plotear_relacion():
  global agg
  # Mapear etiquetas a índices
  all_labels = pd.concat([agg['diet'], agg['length']]).unique()
  label_to_index = {label: index for index, label in enumerate(all_labels)}
  # Crear listas para los nodos y los enlaces
  source = agg['diet'].map(label_to_index).tolist()  # Dietas
  target = agg['length'].map(label_to_index).tolist()  # Longitudes
  values = agg['count'].tolist()  # Conteos

  # Asignar colores a cada tipo de dieta
  diet_colors = {
      'carnivorous': 'rgba(255, 0, 0, 0.8)',  # Rojo para carnívoros
      'herbivorous': 'rgba(0, 255, 0, 0.8)',   # Verde para herbívoros
      'omnivorous': 'rgba(0, 0, 255, 0.8)'      # Azul para omnívoros
  }

  # Crear una lista de colores para cada enlace basado en la dieta
  link_colors = [diet_colors[diet] for diet in agg['diet']]

  # Crear el diagrama de Sankey
  fig = go.Figure(go.Sankey(
      node=dict(
          pad=15,
          thickness=20,
          line=dict(color='black', width=0.5),
          label=all_labels,
          color='blue'  # Color de los nodos
      ),
      link=dict(
          source=source,  # Índices de los nodos de origen
          target=target,  # Índices de los nodos de destino
          value=values,   # Valores de los enlaces
          color=link_colors  # Colores de los enlaces según dieta
      )
  ))

  # Configurar el layout
  fig.update_layout(
  title={
        'text': '¿Qué correlación existe entre el tamaño y la dieta?',
        'font': {
          'size': 15,
          'family': 'monospace'
          }})

  return fig

#Llamar a la función
relacion = plotear_relacion()

# Mostrar el diagrama
relacion.show()

Audiencia: Paleontólogos
Objetivo: Correlacionar la proporcionalidad existente entre el tipo de dieta del dinosaurio y su tamaño.
Visualización: Se usa la librería Plotly de Python. El tipo de gráfica escogida es una Sankey, correlacionando la cantidad de dinosaurios entre las categorías relacionadas por la anchura de las lineas conectoras y su color.
Conjunto de datos: Se ha necesitado realizar una discretización de los tamaños de los dinosaurios por sus cuartiles, luego una agregación de las columnas de interés y por último una creación de listas de etiquetas de “fuentes” y “objetivos” para crear la gráfica. Fuente:https://www.kaggle.com/datasets/kjanjua/jurassic-park-the-exhaustive-dinosaur-dataset

Día 21: Green Energy

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

#Añado estilo preestablecido de plt
plt.style.use('dark_background')
ruta = "datasets/intermittent-renewables-production-france.csv"
df = pd.read_csv(ruta)
print(df.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 59806 entries, 0 to 59805
Data columns (total 9 columns):
 #   Column         Non-Null Count  Dtype  
---  ------         --------------  -----  
 0   Date and Hour  59806 non-null  object 
 1   Date           59806 non-null  object 
 2   StartHour      59806 non-null  object 
 3   EndHour        59806 non-null  object 
 4   Source         59806 non-null  object 
 5   Production     59804 non-null  float64
 6   dayOfYear      59806 non-null  int64  
 7   dayName        59806 non-null  object 
 8   monthName      59806 non-null  object 
dtypes: float64(1), int64(1), object(7)
memory usage: 4.1+ MB
None
#Solo hay dos instancias con nulos, se eliminan
df = df.dropna()

#Se realiza una agregación de la media por mes
df_grouped = df.groupby("monthName")['Production'].mean().reset_index()

# Ordenar los datos por el orden de los meses
month_order = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
df_grouped["monthName"] = pd.Categorical(df_grouped["monthName"], categories=month_order, ordered=True)
df_grouped = df_grouped.sort_values("monthName")

# Convertir los meses a ángulos para el gráfico polar
angles = np.linspace(0, 2 * np.pi, len(df_grouped), endpoint=False).tolist()
angles += angles[:1]  # Repetir el primer ángulo para cerrar el ciclo
values = df_grouped["Production"].tolist()
values += values[:1]  # Repetir el primer valor para cerrar el ciclo

# Configurar el gráfico polar
fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))
ax.plot(angles, values, linewidth=2, color = 'green', linestyle='solid')
ax.fill(angles, values, color = 'green', alpha=0.3)

# Ajustar etiquetas
ax.set_xticks(angles[:-1]) # Usar todos los ángulos excepto el último para evitar duplicados en las etiquetas
ax.set_xticklabels(df_grouped["monthName"])

# Configurar la cuadrícula
ax.grid(color='green', linestyle='--', linewidth=0.7, alpha=0.7)  # Personaliza el color aquí

# Título y mostrar el gráfico
ax.set_title("Producción Media de Energía Verde Mensual 2020-2023", va='bottom')
plt.show()

Audiencia: Skateholders de empresas manufactureras de vehículos eléctricos
Objetivo: Mostrar a inversores del sector eléctrico como se comporta la producción de energía verde para sus vehículos según el mes del año.
Visualización: Se usa Matplotlib de Python. El tipo de visualización es poligonal con un grid polar.
Conjunto de datos: Se crea una agrupación según el mes y se calcula la media de la producción. Tras esto, se sigue procesando ordenando según el mes y realizando diferentes transformaciones para adaptar los datos resultantes al tipo de gráfico que se busca.Fuente:https://www.kaggle.com/datasets/henriupton/wind-solar-electricity-production

Día 22: Mobility

import pandas as pd
import matplotlib.pyplot as plt

#Se reestablece el estilo
plt.style.use('default')

#Se carga el archivo
ruta = "datasets/intermittent-renewables-production-france.csv"
df = pd.read_csv(ruta)

#Solo hay dos instancias con nulos, se eliminan
df = df.dropna()

#Se ordena por la columna dayOfYear
df = df.sort_values(by = 'dayOfYear', ascending = True)

#Se transforma Date en fecha
df['Date'] = pd.to_datetime(df['Date'], format = "%Y-%m-%d")

#Se extrae el año
df['Year'] = df['Date'].dt.year

#Se realiza una agrupación y se calcula la media de la producción
df = df.groupby(['Source', 'dayOfYear', 'Year'])['Production'].mean().reset_index()

#Se crea una función para generar el plot
def create_plot():
  global df
  import seaborn as sns
  sns.set_theme(style="ticks")
  
  g = sns.relplot(x='dayOfYear', y='Production', hue = 'Source', size='Production',
              sizes=(40, 400), alpha=.5, palette=['salmon', 'blue'],
              height=6, col = 'Year', col_wrap = 2, data=df)
  g.set_axis_labels("Día del Año", "Producción de Energía (MWh)")
  g.fig.suptitle("Producción media de Energía a lo largo del año", y = 1.05)
  plt.show()

create_plot()

Audiencia: Skateholders de empresas manufactureras de vehículos eléctricos
Objetivo: Mostrar a los principales skateholders cuál es la mejor fuente de energía verde para mostrar la viabilidad de su negocio sin depender de combustibles fósiles dependiendo del momento del año.
Visualización: Se utiliza la librería Seaborn de Python junto con Matplotlib. El tipo de gráfico es de dispersión aprovechando las ventajas de la función relplot de Seaborn, que permite utilizar un FaceGrid muy cómodamente, además de agregar leyendas. En los distintos años, se observa una distribución de creación de energía similar, comprobando como la energía solar se equipara un poco más con la eólica a mitad de año. Sin embargo, en el resto de las épocas del año, la energía eólica es superior en la producción de energía.
Conjunto de datos: Se realiza un preprocesado transformando la columna de fecha en tipo de formato fecha y así poder extraer el mes, ya que no se tenía antes. A partir de aquí, se puede realizar una agregación múltiple incluyendo el mes, el cual será usado para crear el grid.Fuente:https://www.kaggle.com/datasets/henriupton/wind-solar-electricity-production

Día 23: Tiles

import pandas as pd
import matplotlib.pyplot as plt

#Se reestablece el estilo
plt.style.use('default')

#Se carga el dataframe
ruta = "datasets/intermittent-renewables-production-france.csv"
df = pd.read_csv(ruta)

#Solo hay dos instancias con nulos, se eliminan
df = df.dropna()

#Se ordena por la columna dayOfYear
df = df.sort_values(by = 'dayOfYear', ascending = True)

#Se transforma Date en fecha
df['Date'] = pd.to_datetime(df['Date'], format = "%Y-%m-%d")

#Se extrae el año
df['Year'] = df['Date'].dt.year

#Se realiza la agrupación por año y mes
df = df.groupby(['Year', 'monthName'])['Production'].mean().reset_index()

# Ordenar los datos por el orden de los meses
month_order = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
df["monthName"] = pd.Categorical(df["monthName"], categories=month_order, ordered=True)
df = df.sort_values("monthName")

#Se pivota para crear una tabla donde se crucen las columnas con sus valores
df = df.pivot(index = 'monthName', columns = 'Year', values = 'Production')

plt.figure()
#Se crea un heatmap
ax = sns.heatmap(df, annot = True, linewidths = .5, cmap = 'coolwarm')
# Configurar los nombres de los ejes
ax.set_xlabel('Años')  
ax.set_ylabel('Meses')
# Se añade un título
ax.set_title("¿Cuándo se ha producido más energía verde en los últimos años?", fontsize=10, fontweight='bold', color='darkblue')

plt.show()

Audiencia: Skateholders de empresas manufactureras de vehículos eléctricos
Objetivo: Mostrar a los principales skateholders cuáles han sido los mejores meses de los últimos años en la generación de energía a través de métodos sostenibles.
Visualización: Se ha escogido un heatmap de Seaborn de Python. De esta manera, se puede comprobar cuáles han sido los mejores meses de los últimos años en cuanto a la producción energética verde.
Conjunto de datos: Se realiza un preprocesado transformando la columna de fecha en tipo de formato fecha y así poder extraer el mes. A partir de aquí, se puede realizar una agregación múltiple incluyendo el mes, el cual será usado para crear el grid. Además, para preparar los datos para este tipo de gráfica, se pivota sobre las columnas de mes y año.Fuente:https://www.kaggle.com/datasets/henriupton/wind-solar-electricity-production

Día 24: ILO Region for Africa (data day)

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

#Se reestablece el estilo
plt.style.use('default')

#Se carga el dataframe
ruta = "datasets/SDG_0852_SEX_AGE_RT_A-filtered-2024-10-30.csv"
df = pd.read_csv(ruta)

#Se revisan las primeras filas del archivo
df.head()
  ref_area.label  ...                                  note_source.label
0    Afghanistan  ...  Repository: ILO-STATISTICS - Micro data proces...
1    Afghanistan  ...  Repository: ILO-STATISTICS - Micro data proces...
2    Afghanistan  ...  Repository: ILO-STATISTICS - Micro data proces...
3    Afghanistan  ...  Repository: ILO-STATISTICS - Micro data proces...
4    Afghanistan  ...  Repository: ILO-STATISTICS - Micro data proces...

[5 rows x 11 columns]
#Se filtra por las columnas de interés
df = df[['ref_area.label', 'sex.label', 'classif1.label','time', 'obs_value']]

#Se filtra por sexo = total y mayores de 25 años
df = df[(df['sex.label'] == 'Sex: Total') & (df['classif1.label'] == 'Age (Youth, adults): 25+')]

#Se elimina la columna sex.label, ya no es necesaria
df.drop(['sex.label', 'classif1.label'], axis = 1, inplace = True)

#Se revisan los paises en ref_area.label, ya que se quiere mostrar por separado y no agrupaciones
print(df['ref_area.label'].unique())
['Afghanistan' 'Angola' 'Albania' 'United Arab Emirates' 'Argentina'
 'Armenia' 'Australia' 'Austria' 'Azerbaijan' 'Burundi' 'Belgium' 'Benin'
 'Burkina Faso' 'Bangladesh' 'Bulgaria' 'Bahamas' 'Bosnia and Herzegovina'
 'Belarus' 'Belize' 'Bermuda' 'Bolivia (Plurinational State of)' 'Brazil'
 'Barbados' 'Brunei Darussalam' 'Bhutan' 'Botswana' 'Canada' 'Switzerland'
 'Chile' "Côte d'Ivoire" 'Cameroon' 'Congo, Democratic Republic of the'
 'Cook Islands' 'Colombia' 'Comoros' 'Cabo Verde' 'Costa Rica' 'Curaçao'
 'Cayman Islands' 'Cyprus' 'Czechia' 'Germany' 'Djibouti' 'Denmark'
 'Dominican Republic' 'Algeria' 'Ecuador' 'Egypt' 'Spain' 'Estonia'
 'Ethiopia' 'Finland' 'Fiji' 'Falkland Islands, Malvinas' 'France'
 'Micronesia (Federated States of)'
 'United Kingdom of Great Britain and Northern Ireland' 'Georgia' 'Ghana'
 'Guinea' 'Gambia' 'Guinea-Bissau' 'Greece' 'Grenada' 'Guatemala' 'Guyana'
 'Hong Kong, China' 'Honduras' 'Croatia' 'Hungary' 'Indonesia' 'India'
 'Ireland' 'Iran (Islamic Republic of)' 'Iraq' 'Iceland' 'Israel' 'Italy'
 'Jamaica' 'Jordan' 'Japan' 'Kazakhstan' 'Kenya' 'Kyrgyzstan' 'Cambodia'
 'Kiribati' 'Republic of Korea' 'Kosovo' 'Kuwait'
 "Lao People's Democratic Republic" 'Lebanon' 'Liberia' 'Saint Lucia'
 'Sri Lanka' 'Lesotho' 'Lithuania' 'Luxembourg' 'Latvia' 'Macao, China'
 'Morocco' 'Monaco' 'Republic of Moldova' 'Madagascar' 'Maldives' 'Mexico'
 'Marshall Islands' 'North Macedonia' 'Mali' 'Malta' 'Myanmar'
 'Montenegro' 'Mongolia' 'Mozambique' 'Mauritania' 'Montserrat'
 'Mauritius' 'Malawi' 'Malaysia' 'Namibia' 'New Caledonia' 'Niger'
 'Nigeria' 'Nicaragua' 'Niue' 'Netherlands' 'Norway' 'Nepal' 'Nauru'
 'New Zealand' 'Oman' 'Pakistan' 'Panama' 'Peru' 'Philippines' 'Palau'
 'Papua New Guinea' 'Poland' 'Portugal' 'Paraguay'
 'Occupied Palestinian Territory' 'Qatar' 'Réunion' 'Romania'
 'Russian Federation' 'Rwanda' 'Saudi Arabia' 'Sudan' 'Senegal'
 'Singapore' 'Sierra Leone' 'El Salvador' 'San Marino' 'Somalia' 'Serbia'
 'Sao Tome and Principe' 'Suriname' 'Slovakia' 'Slovenia' 'Sweden'
 'Eswatini' 'Seychelles' 'Chad' 'Togo' 'Thailand' 'Tajikistan' 'Tokelau'
 'Timor-Leste' 'Tonga' 'Trinidad and Tobago' 'Tunisia' 'Türkiye' 'Tuvalu'
 'Taiwan, China' 'Tanzania, United Republic of' 'Uganda' 'Ukraine'
 'Uruguay' 'United States of America' 'Uzbekistan'
 'Venezuela (Bolivarian Republic of)' 'Viet Nam' 'Vanuatu'
 'Wallis and Futuna' 'Samoa' 'World' 'World: Low income'
 'World: Lower-middle income' 'World: Upper-middle income'
 'World: High income' 'Africa' 'Africa: Low income'
 'Africa: Lower-middle income' 'Africa: Upper-middle income'
 'Northern Africa' 'Northern Africa: Lower-middle income'
 'Sub-Saharan Africa' 'Sub-Saharan Africa: Low income'
 'Sub-Saharan Africa: Lower-middle income'
 'Sub-Saharan Africa: Upper-middle income' 'Central Africa'
 'Eastern Africa' 'Southern Africa' 'Western Africa' 'Americas'
 'Americas: Lower-middle income' 'Americas: Upper-middle income'
 'Americas: High income' 'Latin America and the Caribbean'
 'Latin America and the Caribbean: Lower-middle income'
 'Latin America and the Caribbean: Upper-middle income'
 'Latin America and the Caribbean: High income' 'Caribbean'
 'Central America' 'South America' 'Northern America'
 'Northern America: High income' 'Arab States'
 'Arab States: Lower-middle income' 'Arab States: Upper-middle income'
 'Arab States: High income' 'Asia and the Pacific'
 'Asia and the Pacific: Low income'
 'Asia and the Pacific: Lower-middle income'
 'Asia and the Pacific: Upper-middle income'
 'Asia and the Pacific: High income' 'Eastern Asia'
 'Eastern Asia: High income' 'South-Eastern Asia and the Pacific'
 'South-Eastern Asia and the Pacific: Lower-middle income'
 'South-Eastern Asia and the Pacific: Upper-middle income'
 'South-Eastern Asia and the Pacific: High income' 'South-Eastern Asia'
 'Pacific Islands' 'Southern Asia' 'Southern Asia: Lower-middle income'
 'Europe and Central Asia' 'Europe and Central Asia: Lower-middle income'
 'Europe and Central Asia: Upper-middle income'
 'Europe and Central Asia: High income'
 'Northern, Southern and Western Europe'
 'Northern, Southern and Western Europe: Upper-middle income'
 'Northern, Southern and Western Europe: High income' 'Northern Europe'
 'Southern Europe' 'Western Europe' 'Eastern Europe'
 'Eastern Europe: Upper-middle income' 'Eastern Europe: High income'
 'Central Asia' 'Central and Western Asia: Lower-middle income'
 'Central and Western Asia: Upper-middle income'
 'Central and Western Asia: High income' 'Central and Western Asia'
 'Western Asia' 'European Union 28' 'G20' 'ASEAN' 'BRICS'
 'World excluding BRICS' 'G7' 'MENA' 'Arab League' 'CARICOM'
 'European Union 27' 'Arab States: Low income' 'APEC'
 'World excluding India and China'
 'World: Lower-middle income excluding India'
 'World: Upper-middle income excluding China' 'Yemen' 'South Africa'
 'Zambia' 'Zimbabwe']
'''
Se observa que en esta lista no solo hay países, sino también regiones clasificadas por ingresos (World: Low Income, 
Africa: Lower-middle income...), regiones globales y continentes (World, Africa, Americas...), grupos de países y
organizaciones (European Union 28. G20, ASEAN...), otros grupos de exclusión (World excluding India and China...) y 
territorios y áreas especiales (Bermuda, Cook Islands, Hong Kong, China...). Por lo tanto, como se quiere analizar 
púramente paises, se procede a filtrar para excluir a todos estos grupos.
'''
'\nSe observa que en esta lista no solo hay países, sino también regiones clasificadas por ingresos (World: Low Income, \nAfrica: Lower-middle income...), regiones globales y continentes (World, Africa, Americas...), grupos de países y\norganizaciones (European Union 28. G20, ASEAN...), otros grupos de exclusión (World excluding India and China...) y \nterritorios y áreas especiales (Bermuda, Cook Islands, Hong Kong, China...). Por lo tanto, como se quiere analizar \npúramente paises, se procede a filtrar para excluir a todos estos grupos.\n'
exclusion_list = [
    "World", "Africa", "Americas", "Arab States", "Asia and the Pacific", "Europe and Central Asia",
    "Latin America and the Caribbean", "Northern America", "Eastern Asia", "South-Eastern Asia",
    "South-Eastern Asia and the Pacific", "Southern Asia", "Northern, Southern and Western Europe",
    "Northern Europe", "Southern Europe", "Western Europe", "Eastern Europe", "Central Asia",
    "Central and Western Asia", "Western Asia", "Pacific Islands", "Caribbean", "Central America",
    "South America", "Northern Africa", "Sub-Saharan Africa", "Central Africa", "Eastern Africa",
    "Southern Africa", "Western Africa", "European Union 28", "European Union 27", "G20", "G7",
    "ASEAN", "BRICS", "World excluding BRICS", "APEC", "Arab League", "CARICOM", "MENA",
    "World excluding India and China", "World: Lower-middle income excluding India",
    "World: Upper-middle income excluding China", "Bermuda", "Cook Islands", "Curaçao", "Cayman Islands",
    "Falkland Islands, Malvinas", "Hong Kong, China", "Macao, China", "Montserrat", "New Caledonia",
    "Niue", "Occupied Palestinian Territory", "Réunion", "Tokelau", "Taiwan, China", "Wallis and Futuna",
    "South Africa"
]


# Filtrar el DataFrame excluyendo los elementos de la lista y aquellos que contienen ":"
df = df[~df['ref_area.label'].isin(exclusion_list) & ~df['ref_area.label'].str.contains(":")]
#df = df[(~df['ref_area.label'].str.contains(":")) & (~df['ref_area.label'].str.contains("world", case = False))]

#Comprobemos ahora el mínimo y máximo de los años de registros
print(f'Mínimo año: {df['time'].min()}')
Mínimo año: 2014
print(f'Máximo año: {df['time'].max()}')
Máximo año: 2023
#Se almacenan en variables pero se reduce el rango para que no se estudien los extremos. Esto se hace para no reducir demasiado el df.
min_anio = df['time'].min() + 1
max_anio = df['time'].max() - 1

#Se filtra de forma que solo se tengan estos dos valores
df = df[(df['time'] == min_anio) | (df['time'] == max_anio)]

#Se eliminan los paises que no tengan ambos años registrados
country_list = set(df['ref_area.label'].tolist())
ind_to_drop = []
for country in country_list:
    filter = df['ref_area.label'] == country
    df_ = df[filter]
    if min_anio not in df_['time'].values or max_anio not in df_['time'].values:
        ind_to_drop += df[filter].index.to_list()
df.drop(ind_to_drop, axis = 0, inplace = True)
#Se visualiza la cantidad de países restantes
print(f'Cantidad de países a mostrar: {df.shape[0]}')
Cantidad de países a mostrar: 178
#Se pivota sobre el año para facilitar la codificación de la visualización y realizar algún cambio adicional
df = df.pivot(index = 'ref_area.label', columns = 'time', values = 'obs_value').reset_index()

#Se visualiza la nueva forma de la tabla
print(df.head())
time ref_area.label   2015   2022
0         Australia  4.626  2.831
1           Austria  5.054  4.323
2        Azerbaijan  3.810  4.600
3           Belgium  7.304  4.654
4            Bhutan  1.340  3.511
#Se va a calcular la diferencia entre 2014 y 2024 para ver qué países han sido críticos en el cambio
df['diff'] = round(df[max_anio] - df[min_anio], 2)

#Se ordena según el valor absoluto de la diferencia
df = df.sort_values(by = 'diff', key = lambda x: x.abs(), ascending = False)

#Se actualiza la lista de países ordenados según el valor del último año (por motivos de posicion de anotaciones en graficación)
country_list = set(df.sort_values(by = 'diff')['ref_area.label'].to_list())

offset = 5 # para evitar superposiciones en las anotaciones
#Se grafica el slope chart
fig, ax = plt.subplots(figsize = (8, 10))
annotations = []
for country in country_list:
    y1 = df[min_anio][df['ref_area.label'] == country].values[0]
    y2 = df[max_anio][df['ref_area.label'] == country].values[0]
    if country in df['ref_area.label'].head(10).values:
        diff = df['diff'][df['ref_area.label'] == country].values[0]
        #Anotaciones para indicar la diferencia y el país
        offset *= -1 # se va alternando hacia donde se mueve el texto verticalmente
        ann = ax.annotate(f'{country}: {diff} %',
                    (1, y2),
                    textcoords = 'offset points',
                    xytext = (15, 0),
                    ha = 'left',
                    fontsize = 7,
                    fontfamily = 'monospace',
                    color = 'black',
                    arrowprops = dict(
                        arrowstyle = '->',
                        color = 'black'
                    ))
        annotations.append(ann)
        if diff > 0:
            params = {
                'color': 'red',
                'alpha': 0.7,
                'marker': 'o',
                'linewidth': 3,
            }
        else:
            params = {
                'color': 'green',
                'alpha': 0.7,
                'marker': 'o',
                'linewidth': 3,
            }
            if diff == df.head(1)['diff'].values[0]:
                params['color'] = 'darkblue'
            
    else:
        params = {
                'color': 'grey',
                'alpha': 0.1
            }
    plt.plot([0, 1], [y1, y2], **params)
#Se comprueba que no hayan anotaciones pisándose
changed = []
for i, ann1 in enumerate(annotations):
    pos_1 = ann1.xy
    if ann1.get_text() not in changed: #Solo sigue si no se ha movido este annotation ya
        for j, ann2 in enumerate(annotations):
            if i != j: # Evitar cruzamientos consigo mismo
                pos_2 = ann2.xy
                diff_y = abs(pos_1[1] - pos_2[1])
                if diff_y < 0.5:
                    newPosition = (pos_2[0] + 20, pos_2[1] - 12)
                    ann2.set_position(newPosition)
                    #Se a la lista de cambiados, para que no se vuelva a mover ni este ni su "pareja"
                    changed.append(ann2.get_text())


#Anotaciones explicativas
text = f"""Top 10 países con la mayor variación
de tasa de desempleo entre {min_anio} y {max_anio}. 
{df.head(1)['ref_area.label'].values[0]} marca la diferencia con una variación
de un {df.head(1)['diff'].values[0]}%"""


text_params = {
    'color': 'black',
    'ha': 'left',
    'va': 'bottom',
    'fontsize': 7,
    'fontweight': 'bold',
    'fontfamily': 'monospace'
}
ax.text(0.2, df[max_anio].max()+13, text, **text_params)

#Se añaden líneas verticales para marcar inicio y fin
params_lines = {
    'color': 'black',
    'alpha': 0.7,
    'linewidth': 0.7,
    'linestyle': ':'

}
ax.axvline(0, **params_lines)
ax.axvline(1, **params_lines)

#Se agregan los años de comparación
text_params['ha'] = 'center'
text_params['fontsize'] = 10
ax.text(0, df[min_anio].max() + 1.8, min_anio, **text_params)
ax.text(1, df[min_anio].max() + 1.8, max_anio, **text_params)

ax.axis('off')
(-0.05, 1.05, -1.3185499999999999, 29.66955)
ax.set_title("Comparación del ratio de desempleo entre 2015 y 2022", 
             fontfamily = 'monospace', fontweight = 'bold', pad = 30)
plt.tight_layout()
plt.show()

Audiencia: Políticos
Objetivo: Mostrar aquellos países del mundo donde se ha producido un mayor cambio en cuanto a la creación de empleo entre 2015 y 2023.
Visualización: Se utiliza Matplotlib de Python. El tipo de gráfica que se ha escogido es un Slope Chart. Se ha resaltado el top 10 de los países con mayor variación entre los años escogidos. Se utilizan distintos colores para resaltar al que ha tenido un mayor cambio con respecto a los demás. Además, se añaden anotaciones para ver de qué países se trata y el cambio producido.
Conjunto de datos: Se ha escogido una fuente proveniente del tema del día. Se ha hecho un buen preprocesamiento, donde se ha ido filtrando por los datos y columnas de interés con la librería de Pandas. Se podrían haber escogido otras fechas más extremas, como 2014 y 2024. Sin embargo, la cantidad de datos se reducía drásticamente ya que muchos de los países no disponían de datos en esas fechas. El indicador sobre el que se ha calculado la diferencia es el ratio de desempleo, por la SDG (Sustainable Development Goals). Fuente:https://ilostat.ilo.org/topics/sdg/#

Categoría: “Uncertainties”

Día 25: Global Change

import pandas as pd
import plotly.express as px

#Se carga el dataframe
ruta = "datasets/SDG_0852_SEX_AGE_RT_A-filtered-2024-10-30_mod.csv"
df = pd.read_csv(ruta)

#Se revisan las primeras filas del archivo
print(df.head())
  ref_area.label  time  obs_value
0    Afghanistan  2021      4.516
1    Afghanistan  2020      9.792
2    Afghanistan  2017      8.318
3    Afghanistan  2014      6.773
4         Angola  2021     11.015
#Se reorganizan los datos para poder graficarse con plotly

#Se eliminan los na para poder representarse sin problemas
df.dropna(axis = 0, inplace = True)

#Se almacenan en variables pero se reduce el rango para que no se estudien los extremos. Esto se hace para no reducir demasiado el df.
min_anio = df['time'].min() + 1
max_anio = df['time'].max() - 1

#Se filtra el df para excluir los años más extremos
df = df[(df['time']>=min_anio)&(df['time']<=max_anio)]

#Se eliminan los paises que no tengan todos los años intermedios registrados
country_list = set(df['ref_area.label'].tolist())
ind_to_drop = []
for country in country_list:
    filter = df['ref_area.label'] == country
    df_ = df[filter]
    for anio in range(min_anio, max_anio + 1):
        if anio not in df_['time'].values:
            ind_to_drop += df[filter].index.to_list()
            break
df.drop(ind_to_drop, axis = 0, inplace = True)
#Se visualiza la cantidad de países restantes
print(f'Cantidad de países a mostrar: {df.shape[0]}')
Cantidad de países a mostrar: 576
#Se ordena por año
df = df.sort_values(by = 'time')

def plot_animation():
    global df
    #Se crea un gráfico de burbujas animado
    fig = px.scatter(df, x = df['time'], y = df['obs_value'],
                    size = df['obs_value'], color = df['ref_area.label'],
                    animation_frame = df['time'], hover_name = df['ref_area.label'])

    fig.update_layout(xaxis_range = [2013, 2023],
                    yaxis_range = [0, 35],
                     width=800,  
                    height=600,
                     showlegend = False)
    return fig

fig = plot_animation()
fig.show()

Audiencia: Políticos
Objetivo: En conjunción con la gráfica anterior, se muestra como se han ido comportando los ratios de desempleo para los adultos de más de 25 años en los distintos países.
Visualización: Se escoge una visualización animada de Plotly en Python. Concretamente, se anima un Scatter Plot de Burbujas, de forma que se vea según el tamaño una tasa de desempleo mayor o menor y cómo esta va variando.
Conjunto de datos: Se ha aprovechado la misma fuente que la gráfica anterior a partir de un punto de procesamiento y se ha transformado en consonancia con los requerimientos de esta gráfica. Fuente:https://ilostat.ilo.org/topics/sdg/#

Día 26: AI

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib.dates import date2num, DateFormatter
import matplotlib.dates as mdates

# Generar datos de ejemplo
np.random.seed(0)
days = pd.date_range(start='2024-01-01', periods=30, freq='D')
open_prices = np.random.uniform(low=10, high=50, size=len(days))
close_prices = open_prices + np.random.uniform(low=-10, high=10, size=len(days))
high_prices = np.maximum(open_prices, close_prices) + np.random.uniform(low=0, high=5, size=len(days))
low_prices = np.minimum(open_prices, close_prices) - np.random.uniform(low=0, high=5, size=len(days))

data = pd.DataFrame({
    'Date': days,
    'Open': open_prices,
    'Close': close_prices,
    'High': high_prices,
    'Low': low_prices
})

# Ajustar el formato de fecha
data['Date'] = pd.to_datetime(data['Date'])
data['Date'] = date2num(data['Date'])  # Convertir a formato numérico

# Crear el gráfico
plt.figure(figsize=(12, 6), facecolor='black')
plt.title('Gráfico Candlestick de IAforPlotsWithGPT', color='white')
plt.grid(color='gray', linestyle='--', alpha=0.5)

# Dibujar las velas
for index in range(len(data)):
    if data['Close'][index] > data['Open'][index]:
        color = 'green'  # Alcista
        lower = data['Open'][index]
        height = data['Close'][index] - data['Open'][index]
    else:
        color = 'red'  # Bajista
        lower = data['Close'][index]
        height = data['Open'][index] - data['Close'][index]

    plt.bar(data['Date'][index], height, bottom=lower, color=color, alpha=0.7, width=0.5)
    plt.plot([data['Date'][index], data['Date'][index]], [data['Low'][index], data['High'][index]], color=color, alpha=0.7)

# Configurar los ejes
plt.ylabel('Cantidad en Euros', color='white')
plt.xlabel('Días', color='white')
plt.xticks(data['Date'], [day.strftime('%Y-%m-%d') for day in days], rotation=45, color='white')
([<matplotlib.axis.XTick object at 0x0000019D59CFC8C0>, <matplotlib.axis.XTick object at 0x0000019D59CFDA60>, <matplotlib.axis.XTick object at 0x0000019D59F22C90>, <matplotlib.axis.XTick object at 0x0000019D59F47740>, <matplotlib.axis.XTick object at 0x0000019D59EA66F0>, <matplotlib.axis.XTick object at 0x0000019D59CFF140>, <matplotlib.axis.XTick object at 0x0000019D59ED0D70>, <matplotlib.axis.XTick object at 0x0000019D59ED2630>, <matplotlib.axis.XTick object at 0x0000019D59ED3170>, <matplotlib.axis.XTick object at 0x0000019D59EA62D0>, <matplotlib.axis.XTick object at 0x0000019D59EA55E0>, <matplotlib.axis.XTick object at 0x0000019D63695100>, <matplotlib.axis.XTick object at 0x0000019D59EA7710>, <matplotlib.axis.XTick object at 0x0000019D59EA4950>, <matplotlib.axis.XTick object at 0x0000019D59EA4740>, <matplotlib.axis.XTick object at 0x0000019D59EA4CE0>, <matplotlib.axis.XTick object at 0x0000019D59EA4980>, <matplotlib.axis.XTick object at 0x0000019D636975C0>, <matplotlib.axis.XTick object at 0x0000019D59EA4380>, <matplotlib.axis.XTick object at 0x0000019D59ED26F0>, <matplotlib.axis.XTick object at 0x0000019D644593D0>, <matplotlib.axis.XTick object at 0x0000019D6445B260>, <matplotlib.axis.XTick object at 0x0000019D59EA7C20>, <matplotlib.axis.XTick object at 0x0000019D6445AA20>, <matplotlib.axis.XTick object at 0x0000019D59E73AD0>, <matplotlib.axis.XTick object at 0x0000019D59E73290>, <matplotlib.axis.XTick object at 0x0000019D59E728A0>, <matplotlib.axis.XTick object at 0x0000019D59E71B20>, <matplotlib.axis.XTick object at 0x0000019D59F206E0>, <matplotlib.axis.XTick object at 0x0000019D59E73770>], [Text(19723.0, 0, '2024-01-01'), Text(19724.0, 0, '2024-01-02'), Text(19725.0, 0, '2024-01-03'), Text(19726.0, 0, '2024-01-04'), Text(19727.0, 0, '2024-01-05'), Text(19728.0, 0, '2024-01-06'), Text(19729.0, 0, '2024-01-07'), Text(19730.0, 0, '2024-01-08'), Text(19731.0, 0, '2024-01-09'), Text(19732.0, 0, '2024-01-10'), Text(19733.0, 0, '2024-01-11'), Text(19734.0, 0, '2024-01-12'), Text(19735.0, 0, '2024-01-13'), Text(19736.0, 0, '2024-01-14'), Text(19737.0, 0, '2024-01-15'), Text(19738.0, 0, '2024-01-16'), Text(19739.0, 0, '2024-01-17'), Text(19740.0, 0, '2024-01-18'), Text(19741.0, 0, '2024-01-19'), Text(19742.0, 0, '2024-01-20'), Text(19743.0, 0, '2024-01-21'), Text(19744.0, 0, '2024-01-22'), Text(19745.0, 0, '2024-01-23'), Text(19746.0, 0, '2024-01-24'), Text(19747.0, 0, '2024-01-25'), Text(19748.0, 0, '2024-01-26'), Text(19749.0, 0, '2024-01-27'), Text(19750.0, 0, '2024-01-28'), Text(19751.0, 0, '2024-01-29'), Text(19752.0, 0, '2024-01-30')])
plt.yticks(color='white')
(array([-10.,   0.,  10.,  20.,  30.,  40.,  50.,  60.,  70.]), [Text(0, -10.0, '−10'), Text(0, 0.0, '0'), Text(0, 10.0, '10'), Text(0, 20.0, '20'), Text(0, 30.0, '30'), Text(0, 40.0, '40'), Text(0, 50.0, '50'), Text(0, 60.0, '60'), Text(0, 70.0, '70')])
plt.gca().xaxis.set_major_formatter(DateFormatter('%Y-%m-%d'))
plt.gca().xaxis.set_major_locator(mdates.DayLocator(interval=2))
plt.gca().set_facecolor('black')

# Mostrar el gráfico
plt.tight_layout()
plt.show()

Audiencia: Estudiantes de IA
Objetivo: Mostrar lo eficiente que puede ser usar Chatgpt como creador de gráficas de forma rápida y usando el lenguaje más alto que existe de programación: el lenguaje natural.
Visualización: Se utiliza un “CandleStick”, o gráfico de velas, con Matplotlib de Python.
Conjunto de datos: El gráfico ha sido generado artificialmente con Chatgpt usando el siguiente prompt:

Objetivo: Quiero que me generes solamente con Matplotlib y Seaborn en Python un gráfico tipo “Candlestick”.

Contexto: este gráfico representará los valores en el mercado así como sus precios de Inicio y cierre, máximos y mínimos, de una moneda virtual imaginaria llamada IAforPlotsWithGPT.

Detalles de la gráfica:

- Eje y: cantidad en euros

- Eje x: días del 01/01/2024 hasta 30 días después.

- Colores de las velas: los días alcistas en verde y los bajistas en rojo, con una opacidad de alpha = 0.7

- Muestra el grid

- El gráfico tendrá el fondo oscuro.

Día 27: Good/Bad

import pandas as pd
import numpy as np
import plotly.graph_objects as go

#Se carga el archivo
ruta = r"datasets\Student_Satisfaction_Survey.csv"

df = pd.read_csv(ruta, encoding = 'latin1')

#Se revisan las primeras filas del archivo
print(df.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 580 entries, 0 to 579
Data columns (total 12 columns):
 #   Column                Non-Null Count  Dtype 
---  ------                --------------  ----- 
 0   SN                    580 non-null    int64 
 1   Total Feedback Given  580 non-null    int64 
 2   Total Configured      580 non-null    int64 
 3   Questions             580 non-null    object
 4   Weightage 1           580 non-null    int64 
 5   Weightage 2           580 non-null    int64 
 6   Weightage 3           580 non-null    int64 
 7   Weightage 4           580 non-null    int64 
 8   Weightage 5           580 non-null    int64 
 9   Average/ Percentage   580 non-null    object
 10  Course Name           580 non-null    object
 11  Basic Course          580 non-null    object
dtypes: int64(8), object(4)
memory usage: 54.5+ KB
None
#Se toma la suma total de cada campo de resultados de encuesta
muy_malo = df['Weightage 1'].sum()
malo = df['Weightage 2'].sum()
normal = df['Weightage 3'].sum()
bueno = df['Weightage 4'].sum()
muy_bueno = df['Weightage 5'].sum()

#Se calcula una puntuación promedio del 1 al 5, de peor a mejor
puntuaciones = [1] * muy_malo + [2] * malo + [3] * normal + [4] * bueno + [5] * muy_bueno

#Se calcula el valor de la encuesta
promedio = round(np.mean(puntuaciones),1)

# Crear el gráfico de medidor
fig = go.Figure(go.Indicator(
    mode="gauge+number",
    value=promedio,
    title={'text': "Encuesta de Satisfacción"},
    gauge={
        'axis': {'range': [1, 5], 'tickvals': [1, 2, 3, 4, 5], 'ticktext': ['Muy Malo', 'Malo', 'Neutral', 'Bueno', 'Muy Bueno']},
        'bar': {'color': "rgba(255, 255, 0, 0.7)"},
        'steps': [
            {'range': [1, 2], 'color': "rgba(255, 0, 0, 0.7)"},
            {'range': [2, 4], 'color': "rgba(0, 0, 255, 0.7)"},
            {'range': [4, 5], 'color': "rgba(0, 255, 0, 0.7)"},
        ]
    }
))

# Mostrar el gráfico
fig.show()

Audiencia: Coordinador de un Master Universitario
Objetivo: Mostrar el grado de satisfacción general de los alumnos de un master
Visualización: Se utiliza el tipo de gráfico Indicador de la librería Plotly de Python.
Conjunto de datos: Se realiza un recuento de las distintas categorías de satisfacción y se calcula la puntuación media. Fuente:https://www.kaggle.com/datasets/prasad22/student-satisfaction-survey

Día 28: Trend

Audiencia: Economistas y cargos políticos
Objetivo: Enfatizar en la necesidad de disminuir la desigualdad de ingresos con el fin de yo sobrepasar ciertos niveles de GINI ya que, parece ser, existe una relación entre el ratio de delincuencia y este índice.
Visualización: Se escoge la herramienta Datawrapper con el “Scatter plot”. Se ha dividido la gráfica por zonas de peligrosidad según la distribución y la guía de una línea de tendencia cúbica, que es la que parece que mejor se ajusta a la distribución.
Conjunto de datos: No se ha necesitado realizar preprocesamiento más allá de la selección de las columnas adecuadas. Fuente:https://github.com/fivethirtyeight/data/blob/master/hate-crimes/README.md

Día 29: Black’n’White

import nltk
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize
from nltk.stem import WordNetLemmatizer
import re
from wordcloud import WordCloud
import matplotlib.pyplot as plt

def preprocess_text(text):
    # Convertir a minúsculas
    text = text.lower()
    
    # Eliminar caracteres especiales y números
    text = re.sub(r"[^a-zA-Z\s]", "", text)
    
    # Tokenizar el texto
    tokens = word_tokenize(text)
    
    # Eliminar palabras vacías (stopwords)
    stop_words = set(stopwords.words("english"))
    tokens = [word for word in tokens if word not in stop_words]
    
    # Lematización
    lemmatizer = WordNetLemmatizer()
    tokens = [lemmatizer.lemmatize(word) for word in tokens]
    
    # Unir tokens en un solo texto procesado
    processed_text = " ".join(tokens)
    return processed_text

# Leer el archivo de texto
ruta = r"C:\Users\Cristian\Documents\5- Educación\6- Master Universitario en Big Data y Ciencias de Datos\2- Asignaturas\7. Visualización de datos\2. Actividades\Actividad1\30daysChartChallenge\datasets\Black_or_white.txt"
with open(ruta, 'r', encoding='utf-8') as file:
    texto = file.read()
# Ejemplo de uso
texto_procesado = preprocess_text(texto)

wordcloud = WordCloud(width=800, height=400).generate(texto_procesado)

plt.imshow(wordcloud, interpolation='bilinear')
plt.axis("off")
(-0.5, 799.5, 399.5, -0.5)
plt.show()

Audiencia: Concierto Michael Jackson
Objetivo: Decorar el escenario incluyendo este tipo de gráficas artísticas sobre sus canciones. Este es un ejemplo de una de las más populares, Black or White.
Visualización: Se utiliza la librería de wordcloud de Python para la representación de esta gráfica.
Conjunto de datos: Se utilizan técnicas de procesamiento del lenguaje natural (NPL) con la librería nltk de Python para tokenizar, eliminar palabras vacías y lematizar la canción. Fuente: https://genius.com/Michael-jackson-black-or-white-lyrics

Día 30: FiveThirtyEight (theme day)

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

#Se carga el archivo
ruta = r"datasets\avengers.csv"
df = pd.read_csv(ruta, encoding = 'latin1')

#Info
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 173 entries, 0 to 172
Data columns (total 21 columns):
 #   Column                       Non-Null Count  Dtype 
---  ------                       --------------  ----- 
 0   URL                          173 non-null    object
 1   Name/Alias                   163 non-null    object
 2   Appearances                  173 non-null    int64 
 3   Current?                     173 non-null    object
 4   Gender                       173 non-null    object
 5   Probationary Introl          15 non-null     object
 6   Full/Reserve Avengers Intro  159 non-null    object
 7   Year                         173 non-null    int64 
 8   Years since joining          173 non-null    int64 
 9   Honorary                     173 non-null    object
 10  Death1                       173 non-null    object
 11  Return1                      69 non-null     object
 12  Death2                       17 non-null     object
 13  Return2                      16 non-null     object
 14  Death3                       2 non-null      object
 15  Return3                      2 non-null      object
 16  Death4                       1 non-null      object
 17  Return4                      1 non-null      object
 18  Death5                       1 non-null      object
 19  Return5                      1 non-null      object
 20  Notes                        75 non-null     object
dtypes: int64(3), object(18)
memory usage: 28.5+ KB
#Selecciono las columnas de interés
df = df[['Name/Alias', 'Appearances', 'Current?', 'Gender', 'Year', 'Years since joining', 'Years since joining',
         'Honorary']]

#Creo una categoría más: outliers. Esta se va a calcular por separado, para los hombres y para las mujeres
def calc_outliers(data):
    Q1, Q2, Q3 = data.quantile([0.25, 0.5, 0.75])
    IQR = Q3 - Q1
    lim_sup = Q3 + 1.5 * IQR
    return lim_sup

#Llamo a la función para calcular los límites sup e inf de outliers según género
lim_sup_male = calc_outliers(df['Appearances'][df['Gender'] == 'MALE'])
lim_sup_fem = calc_outliers(df['Appearances'][df['Gender'] == 'FEMALE'])

#Instancio una nueva columna con valores None
df['Importance'] = None

#Ahora filtro por género y le doy un valor a la la columna creada en función de los límites de "normalidad"
df['Importance'][df['Gender']=='MALE'] = df['Appearances'][df['Gender']=='MALE'].apply(lambda x: 'VIP' if x >= lim_sup_male else 'Normal')
df['Importance'][df['Gender']=='FEMALE'] = df['Appearances'][df['Gender']=='FEMALE'].apply(lambda x: 'VIP' if x >= lim_sup_fem else 'Normal')

#Ahora se crea una nueva columna combinando el género y su importancia
df['Gender_Importance'] = df['Importance'] + '-' + df['Gender']

def plot_kdes():
  
  #Se establece el estilo
  sns.set_theme(style="white", rc={"axes.facecolor": (0, 0, 0, 0)})
  
  # Initialize the FacetGrid object
  pal = sns.cubehelix_palette(10, rot=-.25, light=.7)
  g = sns.FacetGrid(df, row="Gender_Importance", hue="Gender_Importance", aspect=5, height=3, palette=pal)
  
  # Draw the densities in a few steps
  g.map(sns.kdeplot, "Appearances",
        bw_adjust=.5, clip_on=False,
        fill=True, alpha=0.7, linewidth=1.5)
  g.map(sns.kdeplot, "Appearances", clip_on=False, color="w", lw=2, bw_adjust=.5)
  
  # passing color=None to refline() uses the hue mapping
  g.refline(y=0, linewidth=2, linestyle="-", color=None, clip_on=False)
  
  
  # Define and use a simple function to label the plot in axes coordinates
  def label(x, color, label):
      ax = plt.gca()
      ax.text(0, .2, label, fontweight="bold", color=color,
              ha="left", va="center", transform=ax.transAxes)
  
  g.map(label, "Appearances")
  
  # Define a function to label each facet with unique text
  def add_text_to_facets(g):
    global df
    df.sort_values(by = 'Appearances', ascending = False, inplace = True)
    for ax in g.axes.flat:
        # Se obtiene la importancia y género del facet
        categoria = ax.get_title().replace('Gender_Importance = ', "")
        # Define el TOP 3 de los personajes con más apariciones
        top_3 = df['Name/Alias'][df['Gender_Importance']==categoria].head(3    ).to_list()
        text = f"TOP 3: {",".join(top_3)}"  # Cambia esto por el texto que desees
        # Agrega el texto al facet
        ax.text(0.5, 0.5, text, fontsize=12, ha='center', va='center', transform=ax.transAxes,
                fontweight = 'bold', color = 'black')
  
  # Llama a la función para agregar texto a cada facet
  add_text_to_facets(g)
  
  # Set the subplots to overlap
  g.figure.subplots_adjust(hspace=-.25)
  
  # Remove axes details that don't play well with overlap
  g.set_titles("")
  g.set(yticks=[], ylabel="")
  g.despine(bottom=True, left=True)
  return g


g = plot_kdes()
plt.show()

Audiencia: Exposición sobre comics de Marvel
Objetivo: Presentar al público más aficionado a los comics de Marvel las distribuciones de cantidad de apariciones distinguido por género e importancia. Además, se resalta el top 3 de los personajes que más aparecen dentro de cada grupo.
Visualización: Se usa Seaborn de Python. El tipo de gráfica es una kde (Kernel Density Estimation) para observar el tipo de districución que se da en cada grupo. Se observa que, mientras que el grupo VIP de las mujeres la distribución está más concentrada alrededor de las 1000-1500 apariciones, en el grupo de los hombres VIP se tienen unas colas muy largas y una gráfica de probabilidad poco densa, estando los personajes muy expandidos en apariciones entre 1000 y 4500.
Conjunto de datos: Se ha realizado un preprocesamiento para detectar los outliers por apariciones y asi definir la importancia de la población diferenciada por hombres y mujeres. De esta manera, se puede graficar el kde diferenciado en cuatro categorías, lo cual da una mejor información visual. Fuente:https://github.com/fivethirtyeight/data/blob/master/avengers/avengers.csv

Principales Fuentes y referencias

Se ha usado documentación y recursos externos como apoyo para la creación de todas las gráficas en este documento, las cuales son completamente originales. Fuentes:

Python

R

General